#import "textundo.h"

@implementation TypingTextChange

/*
 * A TypingTextChange object is created whenever and UndoText object wants
 * to insert or delete characters from the keyboard. TypingTextChanges are
 * not atomic like TextSelChanges. That is, no user events can come into the
 * UndoText during a TextSelChange. In contrast, a TypingTextChange has to
 * to wait for each keystroke until the change is completed. A typing change
 * is complete when another change is initiated, or when the UndoText gets
 * a keyDown: not adjacent to the current selection.
 */

#define TYPING_OPERATION NXLocalStringFromTable("Operations", "Typing", NULL, "The operation of typing some text into the document.")

- initView:aView
{
    [super initView:aView name:TYPING_OPERATION];

    insertionPoint = -1;
    subsumingChange = nil;
    firstKeyDown = YES;
    finished = NO;

    return self;
}

- saveBeforeChange
{
    NXSelPt start, end;

    [super saveBeforeChange];

    [textView getSel:&start :&end];
    insertionPoint = start.cp;

    return self;
}

- saveAfterChange
{
    /* Do nothing here. We'll take care of it in finishChange */

    return self;
}

/*
 * The subsumeChange: hook is used to let the typing change know that it
 * should end itself before the next change starts. However, if that change
 * is a delete: and its adjacent to the insertion point, then it was
 * a backspace and we don't want to terminate the typing change.
 *
 * In all the other cases, the new change can never be subsumed by the typing
 * change, but it takes the opportunity to call endChange.
 */
- (BOOL)subsumeChange:change
{
    if ([change isKindOf:[TypingTextChange class]]) {
        [change subsumedBy:self];
        return YES;
    }
    
    if ([change isKindOf:[DeleteTextChange class]] && [self canBeExtended]) {
	return YES;
    }

    return NO;
}

/*
 * This method is called by the ChangeManager when the user undoes this
 * change or when this change doesn't subsume a newly started change.
 */

- finishChange
{
    if (!finished) {
	[super saveAfterChange];
	[self setStart:insertionMin end:insertionMax];
	finished = YES;
    }
    return self;
}

- subsumedBy:change
{
    subsumingChange = change;
    return self;
}

/*
 * A typing change can be extended by a new keystroke if the selection is
 * adjacent to the insertion point maintained by the typing change. So, if
 * the user deletes a character, clicks somewhere else and then deletes
 * another character, two seperate change objects will be created.
 */

- (BOOL)canBeExtended
{
    NXSelPt start, end;
    BOOL returnVal = NO;

    [textView getSel:&start :&end];
    if (start.cp == end.cp) {
	if (start.cp == insertionPoint) {
	    return YES;
	}
    } else if (end.cp == insertionPoint) {
	return YES;
    }

    return returnVal;
}

- deleteCharacter
{
    NXSelPt start, end;
    
    if (subsumingChange != nil)
        return [subsumingChange deleteCharacter];

    if (firstKeyDown) {
	[textView getSel:&start :&end];

	insertionMin = insertionMax = start.cp;

	if (start.cp == end.cp) {
	    if (start.cp > 0) {
		insertionPoint = start.cp - 1;
	    } else {
		insertionPoint = 0;
	    }
	} else {
	    insertionPoint = start.cp;
	}

	firstKeyDown = NO;
    } else {
	if (insertionPoint > 0) {
	    insertionPoint--;
	}
    }
	    
    if (insertionPoint < insertionMin) {
	insertionMin = insertionPoint;
    }

    return self;
}

/*
 * We don't do anything with the character (ch) right now, but a future
 * implementation might want to save each character in a more efficient
 * manner.
 */

- addCharacter:(int)ch
{
    NXSelPt start, end;

    if (subsumingChange != nil)
        return [subsumingChange addCharacter:ch];

    if (firstKeyDown) {
	[textView getSel:&start :&end];

	insertionMin = insertionMax = start.cp;
	insertionPoint = start.cp;
	firstKeyDown = NO;
    }

    insertionPoint++;

    if (insertionPoint > insertionMax) {
	insertionMax = insertionPoint;
    }

    return self;
}

@end
